package ajakuulauku;

import java.util.ArrayList;
import java.util.Vector;

public class CollisionHandler {
	public double frameTime;
	private static Vector<Collision> collisions;

	public CollisionHandler() {
		this.frameTime = 0;
	}
	
	public void checkCollisions() {
		collisions = new Vector<Collision>();
		
		ArrayList<GameObject> balls = CurrentLevel.objectCollections.get("ball");
		
		Ball tmp;
		for (int i = 0; i < balls.size(); i++) {
			tmp = (Ball)balls.get(i);
			
			checkBallCollision(tmp, balls);
			
			checkOtherCollision(tmp);
			
			if (CurrentLevel.hasEdges) {
				checkEdgeCollision(tmp);
			}
			
		}
		
		if (collisions.size() > 0) {
			Collision temp = collisions.get(0);
			
			for (int i = 1; i < collisions.size(); i++) {
				if (collisions.get(i).getFTime() < temp.getFTime()) {
					temp = collisions.get(i);
				}
			}
			frameTime = frameTime + temp.getFTime();
			moveByFrameTime(temp.getFTime());
			
			collide(temp.A, temp.B);
		}
		else {
			moveByFrameTime(1 - frameTime);
			frameTime = 1;
		}
		return;
	}
	
	private void moveByFrameTime(double FT) {
		ArrayList<GameObject> balls = CurrentLevel.objectCollections.get("ball");
		Ball tmp;
		
		for (int i = 0; i < balls.size(); i++) {
			tmp = (Ball)balls.get(i);
			
			//System.out.println(tmp.physics.getMomentum().toString());
			
			Vector2 tmpMomentum = tmp.physics.getMomentum();

			tmpMomentum = tmpMomentum.multiplyBy(FT);

			tmp.setX(tmp.getX() + tmpMomentum.X);
			tmp.setY(tmp.getY() + tmpMomentum.Y);
			//tmp.physics.setCoord(tmp.physics.getCoord().add(tmpMomentum));
			//System.out.println(tmp.physics.getSpeed());
		}
	}
	
	private void checkBallCollision(Ball ball1, ArrayList<GameObject> balls) {
		Ball ball2;
		
		for (int j = balls.indexOf(ball1) + 1; j < balls.size(); j++) {
			ball2 = (Ball) balls.get(j);
			
			ball1.physics.setMomentum(ball1.physics.getMomentum().subtract(ball2.physics.getMomentum()));

			getCollision(ball1, ball2);
			
			ball1.physics.setMomentum(ball1.physics.getMomentum().add(ball2.physics.getMomentum()));

		}
	}
	
	private void checkOtherCollision(Ball ball) {
		GameObject object;
		for (int j = 0; j < CurrentLevel.allObjects.size(); j++) {
			object = CurrentLevel.allObjects.get(j);
			
			if (!(object instanceof Ball) && object.isCollideable) {
				getCollision(ball, object);
			}
		}
	}
	
	private void getCollision(Ball ball, GameObject object) {
		
		Vector2 tmpMomentum = ball.physics.getMomentum().clone().multiplyBy(1-frameTime);
		
		Vector2 collisionVector;
		
		Vector2 distance = new Vector2(object).subtract(new Vector2(ball));
		double dist = distance.length();
		double sumRad = object.getRadius() + ball.getRadius();
		dist = dist - sumRad;
		
		if (tmpMomentum.length() >= dist) {
			
			dist = distance.length();
			
			// tmpMomentum . distance
			double dotProduct = tmpMomentum.clone().normalize().dot(distance);
			

			if (dotProduct > 0) {
				double F = Math.pow(dist, 2) - Math.pow(dotProduct, 2);

				if (F < Math.pow(sumRad, 2)) {
					double T = Math.pow(sumRad, 2) - F;
					
					if (T >= 0) {
						dist = dotProduct - Math.sqrt(T);
						
						if (dist < tmpMomentum.length()) {
							
							collisionVector = tmpMomentum.clone().normalize().multiplyBy(dist);
							double time = collisionVector.length() / tmpMomentum.length();
							
							if (time > 1) {
								time = 1;
							}
							Vector2 collisionPoint = new Vector2(ball.getX(), ball.getY()).add(collisionVector);
							collisions.add(new Collision(time, ball, object, collisionPoint));
						}
					}
				}
			}
		}
	}
	
	
	private void checkEdgeCollision(Ball ball) {
		Vector2 momentum = ball.physics.getMomentum();
		//System.out.println(momentum);
		if (ball.getX() < ball.getRadius()) {
			momentum.X = Math.abs(momentum.X);
		}
		else if (ball.getX() + ball.getRadius() > GUI.WIDTH) {
			momentum.X = -Math.abs(momentum.X);
		}
		else if (ball.getY() < ball.getRadius()) {
			momentum.Y = Math.abs(momentum.Y);
		}
		else if (ball.getY() + ball.getRadius()> GUI.HEIGHT) {
			momentum.Y = -Math.abs(momentum.Y);
		}
		
		ball.physics.setMomentum(momentum);
	}
	
	/**
	 * Changes the ball's moving direction as if the ball had bounced off an object
	 * The moving speed isn't affected in any way
	 * @param collided, int index of the object to collide with
	 */
	public void collide(Ball ball, GameObject collided) {
		
		if (collided instanceof Ball) {
			collideWithBall(ball, (Ball)collided);
		}
		else {
			//System.out.println(ball.physics.getMomentum().length());
			//Vector2 a = new Vector2(collided);
			
			ball.physics.setMomentum(getDirection(ball, collided));
			
			//System.out.println(ball.physics.getMomentum().length());
		}
		
	}
	
	private void collideWithBall(Ball ball1, Ball ball2) {		
		
		double speed1 = ball1.physics.getSpeed();
		double mass1 = ball1.physics.getMass();
		Vector2 momentum1 = ball1.physics.getMomentum().clone().setLength(speed1).multiplyBy(mass1);

		
		double speed2 = ball2.physics.getSpeed();
		double mass2 = ball2.physics.getMass();
		Vector2 momentum2 = ball2.physics.getMomentum().clone().setLength(speed2).multiplyBy(mass2);
		
		Vector2 normaal = new Vector2(ball1).subtract(new Vector2(ball2)).normalize();
		
		Vector2 straight1 = normaal.multiplyBy(normaal.dot(momentum1));
		Vector2 straight2 = normaal.multiplyBy(normaal.dot(momentum2));
		
		Vector2 across1 = momentum1.subtract(straight1);
		Vector2 across2 = momentum2.subtract(straight2);
		
		momentum1 = across1.add(straight2);
		momentum2 = across2.add(straight1);

		ball1.physics.setMomentum(momentum1.divideBy(mass1));
		ball2.physics.setMomentum(momentum2.divideBy(mass2));
	}
	
	private Vector2 getDirection(Ball ball, GameObject object ) {
		Vector2 tmpMomentum = ball.physics.getMomentum();
		double tmpSpeed = tmpMomentum.length();
		
		Vector2 straight = new Vector2();
		Vector2 across = new Vector2();
		
		Vector2 normaal = new Vector2(object).subtract(new Vector2(ball)).normalize();
		
		double multi = Math.abs(normaal.X * tmpMomentum.X + normaal.Y * tmpMomentum.Y);
		
		if (tmpMomentum.length() == 0) {
			straight = normaal;
		}
		else {
			straight = normaal.multiplyBy(multi);			
		}
		
		across = tmpMomentum.subtract(straight);
		
		tmpMomentum = across.subtract(straight);
		
		return tmpMomentum.setLength(tmpSpeed);
	}
	/*
	private Vector2 getDirection(Ball ball1, Ball ball2 ) {
		Vector2 tmpCoord = new Vector2(ball1);
		Vector2 tmpMomentum = ball1.physics.getMomentum();
		Vector2 collided =  new Vector2(ball2);
		
		Vector2 straight = new Vector2();
		Vector2 across = new Vector2();
		
		Vector2 normaal = new Vector2(collided.X - tmpCoord.X, collided.Y - tmpCoord.Y);
		
		normaal = normaal.normalize();
		
		double multi = Math.abs(normaal.X * tmpMomentum.X + normaal.Y * tmpMomentum.Y);
		
		if (tmpMomentum.length() == 0) {
			straight = normaal;
		}
		else {
			straight = normaal.multiplyBy(multi);			
		}
		
		across = tmpMomentum.subtract(straight);
		
		tmpMomentum = across.subtract(straight);
		
		return tmpMomentum;
	}
	*/
}
